﻿#region Disclaimer/Info

/////////////////////////////////////////////////////////////////////////////////////////////////
//
//   File:		SessionFactory.cs
//   Website:		http://dexterblogengine.com/
//   Authors:		http://dexterblogengine.com/About.ashx
//   Rev:		1
//   Created:		19/01/2011
//   Last edit:		19/01/2011
//   License:		GNU Library General Public License (LGPL)
//   File:            SessionFactory.cs
//   For updated news and information please visit http://dexterblogengine.com/
//   Dexter is hosted to Codeplex at http://dexterblogengine.codeplex.com
//   For any question contact info@dexterblogengine.com
//
///////////////////////////////////////////////////////////////////////////////////////////////////

#endregion

using System;
using System.Data;
using Dexter.CallContext;
using Dexter.Logger.Contracts;
using Dexter.Repository.NHibernate.Helpers;
using NHibernate;

namespace Dexter.Repository.NHibernate.SessionFactory
{
	/// <summary>
	/// The implemenation of <see cref="ISessionFactory"/>.
	/// </summary>
	public class SessionFactory : ISessionFactory
	{
		internal const String SESSION_KEY = "{9D59CC3E-E68A-49B0-9874-5D76BBBC39A8}";
		internal const String TRANSACTION_KEY = "{346d94e4-6e90-4df4-a93f-4995de273ec3}";
		internal const String SESSION_STATE_KEY = "{740123ac-1ef2-422f-b8d9-019bf3f36e1e}";

		readonly ICallContextFactory callContextFactory;
		readonly INHibernateHelper nHibernateHelper;
		readonly ILogger logger;

		/// <summary>
		/// Initializes a new instance of the <see cref="SessionFactory"/> class.
		/// </summary>
		/// <param name="callContextFactory">The call context factory.</param>
		/// <param name="nHibernateHelper">The n hibernate helper.</param>
		/// <param name="logger">The logger.</param>
		public SessionFactory(ICallContextFactory callContextFactory, INHibernateHelper nHibernateHelper , ILogger logger )
		{
			this.callContextFactory = callContextFactory;
			this.logger = logger;
			this.nHibernateHelper = nHibernateHelper;
		}

		/// <summary>
		/// Releases unmanaged resources and performs other cleanup operations before the
		/// <see cref="SessionFactory"/> is reclaimed by garbage collection.
		/// </summary>
		~SessionFactory()
		{
			this.CloseCurrentSession(false);
		}

		#region ISessionFactory Members

		/// <summary>
		/// Prepares the sesson.
		/// </summary>
		/// <param name="isReadOnly">if set to <c>true</c> [is read only].</param>
		public void PrepareSesson ( bool isReadOnly ) {
			ICallContext currentContext = callContextFactory.RetrieveCallContext ( );

			currentContext[ SESSION_STATE_KEY ] = isReadOnly;
		}

		/// <summary>
		/// Create a session in the current context. If the context already created a
		/// session, then the active transaction is returned
		/// </summary>
		/// <returns></returns>
		public ISession GetSession()
		{
			ICallContext currentContext = callContextFactory.RetrieveCallContext();

			ISession session;

			if (currentContext[SESSION_KEY] == null)
			{
				logger.Debug ( "Session·Created" );
				session = CreateAndPutSessionInContext(currentContext);
			}
			else
			{
				//Session is already in context, but it could be disposed
				logger.Debug ( "Session·was·already·created" );

				session = (ISession)currentContext[SESSION_KEY];
				if (!session.IsOpen)
				{
					session = CreateAndPutSessionInContext(currentContext);
				}
			}
			return session;
		}

		/// <summary>
		/// This call close the current opened session, and commit/rollback the
		/// transaction if the session was created in transaction.
		/// <remarks>In this first version of the session factory, the session is always
		/// in transaction.</remarks>
		/// </summary>
		/// <param name="commitIfInTransaction"></param>
		public void CloseCurrentSession ( bool commitIfInTransaction ) {
			ICallContext currentContext = callContextFactory.RetrieveCallContext ( );

			if (currentContext == null)
				return;

			if (currentContext[SESSION_KEY] == null)
				return;

			using (var session = ( ISession )currentContext[SESSION_KEY]) {
				try {
					if (session.IsOpen) {
						if (session.Transaction != null) {
							using (session.Transaction) {
								if (session.Transaction.IsActive) {
									if (commitIfInTransaction)
										try {
											session.Transaction.Commit ( );
										}
										catch (Exception ex) {
											logger.Error ( "Exception during commit of transaction", ex );
											session.Transaction.Rollback ( );
											throw;
										}
									else {
										session.Transaction.Rollback ( );
									}
								}
							}
						}
						else {
							session.Flush ( );
						}
					}
				}
				finally {
					if (session.Transaction != null) session.Transaction.Dispose ( );
					currentContext[SESSION_KEY] = null;
					currentContext[TRANSACTION_KEY] = null;
					currentContext[SESSION_STATE_KEY] = null;
				}
			}
		}

		#endregion

		internal ISession CreateAndPutSessionInContext(ICallContext dexterContext)
		{
			ISession session = nHibernateHelper.GetSession();

			var sessionStateConfiguration = dexterContext[ SESSION_STATE_KEY ];
			var isReadOnly = sessionStateConfiguration == null
			                   	? false
			                   	: ( bool ) sessionStateConfiguration;

			if (isReadOnly) {
				session.DefaultReadOnly = true;
				session.FlushMode = FlushMode.Never;
			}
			else {
				session.FlushMode = FlushMode.Commit;
			}
			
			dexterContext[SESSION_KEY] = session;
			dexterContext[TRANSACTION_KEY] = session.BeginTransaction(IsolationLevel.ReadCommitted);
			return session;
		}
	}
}
